package com.weixin.project.Service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.weixin.project.Common.Enum.ClassField;
import com.weixin.project.Common.Enum.EntityType;
import com.weixin.project.Common.Enum.QuestionUploadStatus;
import com.weixin.project.Common.Enum.UndoType;
import com.weixin.project.Common.Exception.EntityNotExistException;
import com.weixin.project.Common.Exception.JSONFieldIncorrectException;
import com.weixin.project.Dao.*;
import com.weixin.project.Entity.*;
import com.weixin.project.Specification.QuestionSpecification;
import com.weixin.project.Util.JSONUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@SuppressWarnings({"unchecked", "rawtypes"})
@Service
public class QuestionService {
    @Autowired
    QuestionBankDao questionBankDao;
    @Autowired
    QuestionDao questionDao;
    @Autowired
    ChapterDao chapterDao;
    @Autowired
    AnswerDao answerDao;
    @Autowired
    QuestionUploadRecordDao questionUploadRecordDao;
    @Autowired
    UndoService undoService;
    @Autowired
    OSSService ossService;
    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 题目
     * spring cache, key = #questionId
     * question entity
     */
    private static final String QUESTION = "question";

    /**
     * 所有题目
     * set, setKey = ALL_QUESTIONS_SET
     * element = questionId
     */
    private static final String ALL_QUESTIONS_SET = "all_questions";

    /**
     * 题库
     * spring cache, key = #bankId
     * questionBank entity
     */
    private final static String QUESTION_BANK = "question_bank";

    private final static String QUESTION_BANKS = "question_banks";

    /**
     * 章节
     * spring cache, key = 'all'
     * chapter entity
     */
    private final static String CHAPTER = "chapter";

    /**
     * 题库下的所有章节
     * spring cache, key = #bankId
     * list of chapter entity
     */
    private final static String CHAPTERS = "chapters";

    /**
     * 题目的所有答案
     * spring cache, key = #questionId
     * list of answer entity
     */
    private final static String ANSWERS_OF_QUESTION = "answers";

    public List<QuestionBank> findAllQuestionBanks(){
        return questionBankDao.findAll();
    }

    @Cacheable(value = QUESTION_BANK, key = "#id", unless = "#result == null ")
    public QuestionBank findQuestionBankById(Integer id){
        Optional<QuestionBank> questionBankOp = questionBankDao.findById(id);
        return questionBankOp.orElse(null);
    }

    public List<Chapter> findChapters(Integer questionBankId){
        return chapterDao.getChaptersByQuestionBankId(questionBankId);
    }

    @Cacheable(value = CHAPTER, key = "#chapterId", unless = "#result == null")
    public Chapter findChapter(Integer chapterId){
        Optional<Chapter> chapterOp = chapterDao.findById(chapterId);
        return chapterOp.isPresent()?chapterOp.get():null;
    }

    @Cacheable(value = ANSWERS_OF_QUESTION, key = "#questionId")
    public List<Answer> findAnswers(Integer questionId){
        return answerDao.getAnswersByQuestionIdOrderById(questionId);
    }

    /**
     * 根据page查找问题
     * @param chapterId 章节id
     * @param page 页号，一页一个问题
     * @return
     */
    public Question findQuestionByChapterId(Integer chapterId, Integer page){
        return questionDao.getQuestionByChapterIdAndPosition(chapterId, page);
    }

    @Cacheable(value = QUESTION, key = "#Id", unless = "#result == null")
    public Question findQuestionById(Integer Id){
        Optional<Question> questionOp = questionDao.findById(Id);
        return questionOp.orElse(null);
    }

    public Integer countQuestionsOfChapter(Integer chapterId){
        return questionDao.countQuestionsByChapterId(chapterId);
    }

    public Integer countQuestionsOfBank(Integer bankId){
        return questionDao.countQuestionsByQuestionBankId(bankId);
    }

    /**
     * 获取所有的题目id集合
     * @return 所有的题目id集合
     */
    public Set<Integer> allQuestionsSet(){
        if (!redisTemplate.hasKey(ALL_QUESTIONS_SET)){
            List<Question> allQuestions = questionDao.findAll();
            Set<Integer> set = allQuestions.stream().map(x -> x.getId()).collect(Collectors.toSet());
            set.forEach(x -> redisTemplate.boundSetOps(ALL_QUESTIONS_SET).add(x));
        }
        return redisTemplate.boundSetOps(ALL_QUESTIONS_SET).members();
    }

    public List<Question> searchQuestionsWithTokens(String[] tokens) {
        List<Question> questions = questionDao.findAll(QuestionSpecification.stemContainsTokens(tokens));
        // 对结果进行排序
        return questions.stream()
                .filter(question -> countTokensInString(question.getStem(), tokens) >= tokens.length/2 )
                .sorted(Comparator.comparingInt((Question question) -> countTokensInString(question.getStem(), tokens)).reversed())
                .collect(Collectors.toList());
    }

    private int countTokensInString(String stem, String[] tokens) {
        int count = 0;
        for (String token : tokens) {
            count += stem.split(token, -1).length - 1;
        }
        return count;
    }

    public QuestionUploadRecord previewQuestionUploadResults(MultipartFile file) throws IOException {
        String path = ossService.uploadJSON(file);
        String jsonStr = ossService.readJsonFileFromOSS(path);
        return previewQuestionUploadResults(path, jsonStr);
    }

    public QuestionUploadRecord previewQuestionUploadResults(String path, String jsonStr) {
        //检查是否是一个JSON文件
        JSONUtil.validateJSONFile(jsonStr);
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        //检查是否符合JSON schema
        JSONUtil.checkSchemaOfJSON(jsonObject);
        //检查题目和答案的格式
        checkQuestionAnswersFormat(jsonObject);

        //以上步骤出现异常将直接返回错误信息给前端

        QuestionUploadRecord record = new QuestionUploadRecord();
        record.setCreateTime(new Date());
        record.setJsonStr(jsonObject.toJSONString());
        record.setPreview(generatePreview(jsonObject));
        record.setStatus(QuestionUploadStatus.TO_BE_UPLOADED);
        record.setUuid(UUID.randomUUID().toString());
        record.setJsonPath(path);
        record.setBankName(jsonObject.getJSONObject("question_bank").getString("bank_name"));
        questionUploadRecordDao.save(record);
        return record;
    }

    private void checkQuestionAnswersFormat(JSONObject json){
        final List<String> typeList = Arrays.asList("单选题", "多选题", "填空题", "判断题");
        JSONObject questionBank = json.getJSONObject("question_bank");
        JSONArray chapters = questionBank.getJSONArray("chapters");
        for (int i = 0; i < chapters.size() ; i++) {
            JSONObject chapter = chapters.getJSONObject(i);
            JSONArray questions = chapter.getJSONArray("questions");
            for (int j = 0; j < questions.size(); j++) {
                JSONObject question = questions.getJSONObject(j);
                JSONArray answers = question.getJSONArray("answers");
                String type = question.getString("type");
                String stem = question.getString("stem");
                if (!typeList.contains(type)) throw new JSONFieldIncorrectException(stem);
                checkCountOfAnswers(type, answers, stem);
                checkContentOfAnswers(type, answers, stem);
            }
        }
    }

    private void checkCountOfAnswers(String type, JSONArray answers, String stem){
        switch (type){
            case "单选题":{
                //单选题4个选项，有且仅有一个对的
                if (answers.size() != 4 || countRightAnswers(answers) != 1){
                    throw new JSONFieldIncorrectException(stem);
                }
                break;
            }
            case "多选题":{
                //多选题4个选项，至少有一个对的
                if (answers.size() != 4 || countRightAnswers(answers) == 0){
                    throw new JSONFieldIncorrectException(stem);
                }
                break;
            }
            case "填空题":{
                //填空题空的个数等于答案的个数，且所有的答案的is_right均为true
                if (countBlanks(stem) == 0 ||
                        countBlanks(stem) != answers.size() ||
                        answers.size()!=countRightAnswers(answers))
                    throw new JSONFieldIncorrectException(stem);
            }
            case "判断题":{
                //判断题的答案个数为1，且is_right为true
                if (answers.size() != 1 || countRightAnswers(answers) != 1){
                    throw new JSONFieldIncorrectException(stem);
                }
                break;
            }
        }
    }

    private Integer countRightAnswers(JSONArray answers){
        Integer count = 0;
        for (int i = 0; i < answers.size(); i++) {
            JSONObject obj = answers.getJSONObject(i);
            if (obj.getBoolean("isRight")){
                count ++ ;
            }
        }
        return count;
    }

    private Integer countBlanks(String input){
        String pattern = "___";
        int count = 0;
        int index = 0;

        while ((index = input.indexOf(pattern, index)) != -1) {
            count++;
            index += pattern.length();
        }
        return count;
    }

    private void checkContentOfAnswers(String type, JSONArray answers, String stem){
        final List<String> judges = Arrays.asList("1", "0");
        if (type.equals("判断题")){
            for (int i = 0; i < answers.size(); i++) {
                if  (!judges.contains(answers.getJSONObject(i).getString("content"))){
                    throw new JSONFieldIncorrectException(stem);
                }
            }
        }
    }

    private String generatePreview(JSONObject json){
        StringBuilder sb = new StringBuilder();
        JSONObject questionBank = json.getJSONObject("question_bank");
        if (questionBankDao.getQuestionBankByBankName(questionBank.getString("bank_name")) == null)
            sb.append("Warning: This operation will add a new questionBank:"
                + questionBank.getString("bank_name") + " into DB.\n\n");
        JSONArray chapters = questionBank.getJSONArray("chapters");
        Integer questionCount = 0;
        Integer answerCount = 0;
        for (int i = 0; i < chapters.size() ; i++) {
            JSONObject chapter = chapters.getJSONObject(i);
            if (chapterDao.getChapterByChapterName(chapter.getString("chapter_name")) == null)
                sb.append("Warning: This operation will add a new chapter:"
                        + questionBank.getString("chapter_name") + " into DB.\n\n");
            JSONArray questions = chapter.getJSONArray("questions");
            questionCount += questions.size();
            for (int j = 0; j < questions.size(); j++) {
                JSONObject question = questions.getJSONObject(j);
                JSONArray answers = question.getJSONArray("answers");
                answerCount += answers.size();
            }
        }
        sb.append("Warning: This operation will add " + questionCount + " questions into DB\n\n");
        sb.append("Warning: This operation will add " + answerCount + " answers into DB\n\n");
        sb.append("Are you sure to continue?");
        return sb.toString();
    }

    public void confirmUpload(Integer recordId, Boolean confirm){
        if (!confirm) {
            cancelUpload(recordId);
        } else {
            uploadQuestion(recordId);
        }
    }

    private void cancelUpload(Integer recordId){
        Optional<QuestionUploadRecord> recordOptional = questionUploadRecordDao.findById(recordId);
        if (!recordOptional.isPresent()) throw new EntityNotExistException("QuestionUploadRecord", recordId);
        QuestionUploadRecord record = recordOptional.get();
        record.setStatus(QuestionUploadStatus.CANCELED);
        questionUploadRecordDao.save(record);
    }

    @Transactional
    public void uploadQuestion(Integer recordId){
        Map<String, Integer> typeMap = new HashMap<>();
        typeMap.put("单选题", 1);
        typeMap.put("多选题", 2);
        typeMap.put("判断题", 3);
        typeMap.put("填空题", 4);
        Optional<QuestionUploadRecord> recordOptional = questionUploadRecordDao.findById(recordId);
        if (!recordOptional.isPresent()) throw new EntityNotExistException("QuestionUploadRecord", recordId);
        QuestionUploadRecord record = recordOptional.get();
        if (!record.getStatus().equals(QuestionUploadStatus.TO_BE_UPLOADED)){
            System.out.println("Error! " + "This json file has already been uploaded");
            return;
        }
        JSONObject json = JSONObject.parseObject(record.getJsonStr());
        JSONObject questionBankObj = json.getJSONObject("question_bank");
        QuestionBank questionBank = questionBankDao.getQuestionBankByBankName(questionBankObj.getString("bank_name"));
        if (questionBank == null){
            questionBank = new QuestionBank();
            questionBank.setBankName(questionBankObj.getString("bank_name"));
            questionBank.setClassField(ClassField.chineseNameOf(questionBankObj.getString("class")));
            save(questionBank, record.getUuid());
        }
        JSONArray chapterObjs = questionBankObj.getJSONArray("chapters");
        for (int i = 0; i < chapterObjs.size() ; i++) {
            JSONObject chapterObj = chapterObjs.getJSONObject(i);
            Chapter chapter = chapterDao.getChapterByChapterName(chapterObj.getString("chapter_name"));
            if (chapter == null){
                chapter = new Chapter();
                chapter.setQuestionBankId(questionBank.getId());
                chapter.setChapterName(chapterObj.getString("chapter_name"));
                chapter.setFree(chapterObj.getBoolean("free"));
                save(chapter, record.getUuid());
            }
            JSONArray questionObjs = chapterObj.getJSONArray("questions");
            for (int j = 0; j < questionObjs.size(); j++) {
                JSONObject questionObj = questionObjs.getJSONObject(j);
                Question question = new Question();
                question.setClassField(ClassField.chineseNameOf(questionBankObj.getString("class")));
                question.setQuestionBankId(questionBank.getId());
                question.setChapterId(chapter.getId());
                question.setStem(questionObj.getString("stem"));
                question.setDifficulty(questionObj.getInteger("difficulty"));
                question.setExplanation(questionObj.getString("explain"));
                //这个章节前面有多少题目，那么position就是多少
                question.setPosition(countQuestionsOfChapter(chapter.getId()));
                question.setTypeId(typeMap.get(questionObj.getString("type")));
                save(question, record.getUuid());
                JSONArray answerObjs = questionObj.getJSONArray("answers");
                for (int k = 0; k < answerObjs.size(); k++) {
                    JSONObject answerObj = answerObjs.getJSONObject(k);
                    Answer answer = new Answer();
                    answer.setContent(answerObj.getString("content"));
                    answer.setRightAnswer(answerObj.getBoolean("isRight"));
                    answer.setQuestionId(question.getId());
                    save(answer, record.getUuid());
                }
            }

        }
        record.setStatus(QuestionUploadStatus.UPLOADED);
        questionUploadRecordDao.save(record);
    }

    private void save(QuestionBank bank, String uuid){
        questionBankDao.save(bank);
        undoService.addUndoLog(EntityType.QUESTION_BANK, bank.getId(), UndoType.DELETE, uuid);
    }

    private void save(Chapter chapter, String uuid){
        chapterDao.save(chapter);
        undoService.addUndoLog(EntityType.CHAPTER, chapter.getId(), UndoType.DELETE, uuid);
    }

    private void save(Question question, String uuid){
        questionDao.save(question);
        undoService.addUndoLog(EntityType.QUESTION, question.getId(), UndoType.DELETE, uuid);
    }

    private void save(Answer answer, String uuid){
        answerDao.save(answer);
        undoService.addUndoLog(EntityType.ANSWER, answer.getId(), UndoType.DELETE, uuid);
    }

    public List<QuestionUploadRecord> viewQuestionUploadRecords(){
        return questionUploadRecordDao.getQuestionUploadRecordsByStatus(QuestionUploadStatus.UPLOADED);
    }

    public void rollbackUpload(Integer recordId){
        Optional<QuestionUploadRecord> recordOp = questionUploadRecordDao.findById(recordId);
        if (!recordOp.isPresent()) throw new EntityNotExistException("QuestionUploadRecord", recordId);
        QuestionUploadRecord record = recordOp.get();
        undoService.undo(record.getUuid());
        record.setStatus(QuestionUploadStatus.ROLLBACK);
        questionUploadRecordDao.save(record);
    }
}
